package org.xtext.runtime.control;

import is.lill.acre.protocol.IRepository;
import is.lill.acre.protocol.RepositoryException;
import is.lill.acre.protocol.RepositoryFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.xtext.runtime.acre.ACREService;
import org.xtext.runtime.eis.EISService;
import org.xtext.runtime.formula.Predicate;
import org.xtext.runtime.interpreter.ASTRAClass;
import org.xtext.runtime.interpreter.ASTRAClassNotFoundException;
import org.xtext.runtime.interpreter.Agent;
import org.xtext.runtime.interpreter.DuplicateAgentNameException;
import org.xtext.runtime.term.ITerm;
import org.xtext.runtime.term.Term;
import cartago.CartagoException;
import cartago.CartagoService;

/**
 * Start point for running agents via a properties file (.mas). Specifically, this
 * class runs the multi-agent system in deployment mode (no gui).
 * 
 * @author rem
 *
 */
public class Loader {
	private List<Agent> agents = new LinkedList<Agent>();
	private Executor[] executors;
	
	public void load(String id) {
		Properties properties = new Properties();
		try {
			FileInputStream in = new FileInputStream(new File(id));
			properties.load(in);
			in.close();
		} catch (FileNotFoundException e1) {
			System.err.println( "Configuration file \"" + id + "\" not found: QUITTING" );
			System.exit(1);
		} catch (IOException e1) {
			System.err.println( "Configuration file \"" + id + "\" could not be read: QUITTING" );
			System.exit(1);
		}

		// Install messaging service
		String messagingService = properties.getProperty("message_service");
		if (messagingService != null) {
			System.err.print("Installing Messaging Service: " + messagingService);
			try {
				Class<?> msClass = Class.forName(messagingService);
				msClass.newInstance();
				System.err.println(" SUCCESS");
			} catch (ClassNotFoundException e1) {
				System.err.println(" FAILED\n\tNo service named " + messagingService + " could be found.");
			} catch (InstantiationException e1) {
				System.err.println(" FAILED\n\tThe service " + messagingService + " could not be loaded");
			} catch (IllegalAccessException e1) {
				System.err.println(" FAILED\n\tThe service " + messagingService + " could note be accessed");
			} 
		}
		
		String acreLaunch = properties.getProperty("use_acre", "no");
		String cartagoLaunch = properties.getProperty("use_cartago", "no");
		
		if (cartagoLaunch.equals("yes")) {
			System.err.println("Starting Cartago Service");
			try {
				CartagoService.startNode();
			} catch (CartagoException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		// acre_logging parameter should set a known java.util.Level (defaults to 'off') 
		if (acreLaunch.equals("yes")) {
			Logger logACRE = Logger.getLogger( "is.lill.acre" );
			String acreLogging = properties.getProperty("acre_logging", "off");
			try {
				Level logLevel = Level.parse( acreLogging.toUpperCase() );
				Handler consoleHandler = new ConsoleHandler();
				consoleHandler.setLevel( logLevel );
				logACRE.setLevel( logLevel );
				logACRE.addHandler( consoleHandler );
			}
			catch ( IllegalArgumentException e ) {
				System.err.println( "Unrecognised logging level \"" + acreLogging + "\" for acre_logging: ACRE LOGGING DISABLED" );
				logACRE.setLevel( Level.OFF );
			}
			logACRE.info( "ACRE Logging level set to " + logACRE.getLevel().toString() );
			
			
			// load any repositories defined in the configuration
			if ( properties.containsKey( "acre_repository") ) {
				String[] repositories = properties.getProperty("acre_repository").split( "," );
				for ( String r : repositories ) {
					System.out.print( "Loading ACRE repository \"" + r + "\":");
					try {
						IRepository rep = RepositoryFactory.openRepository( r );
						ACREService.manager.addRepository(rep);
						System.out.println(" SUCCESS");
					} catch (RepositoryException e1) {
						System.err.println( " FAILED" );
					}
				}
			}
		}
		
		// Check if EIS is being used
		String eisJar = properties.getProperty("eis-jar");
		String eisId = properties.getProperty("eis-id");
		String defaultClass = properties.getProperty("eis-default-class", null);
		EISService service = null;
		List<String> names = new LinkedList<String>();
		if (eisJar != null) {

		   service = EISService.getService(eisId, eisJar);
			for (String entity : service.getFreeEntities()) {
				// Check if the agent uses a custom class or the default class
				String cls = properties.getProperty(entity);
				if (cls == null) cls = defaultClass;

				if (cls != null) {
					try {
						// Create the agent...
						Agent agent = ASTRAClass.forName(cls).newInstance(entity);
						agents.add(agent);
						names.add(entity);
						
						// Link to cartago (if necessary)
						if (cartagoLaunch.equals("yes")) {
							agent.startCartago();
						}
						
						if (acreLaunch.equals("yes")) {
							agent.startACRE();
						}
						
						// Initialize the agent
						service.registerAgent(agent);
						service.associateEntity(agent, entity);
						agent.initialize(new Predicate("service", 
								new ITerm[] { Term.newInstance(eisId) }
						));
					} catch (ASTRAClassNotFoundException e1) {
						e1.printStackTrace();
					} catch (DuplicateAgentNameException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				}
			}
		}
		
	
		// Read the list of entities for the configuration and load them - any declared
		// as eis entities will be ignored...
		if (properties.containsKey("entities")) {
			for (String entity : properties.getProperty("entities").split(",")) {
	
				// first element of a split will be an empty string if no entities were provided
				if ( ! entity.equals( "" ) ) {
					try {
						if (!names.contains(entity)) {
							if ( properties.containsKey( entity ) ) {
								Agent agent = ASTRAClass.forName(properties.getProperty(entity)).newInstance(entity);
								agents.add(agent);
								names.add(entity);
								
								// Link to cartago (if necessary)
								if (cartagoLaunch.equals("yes")) {
									agent.startCartago();
								}
			
								if (acreLaunch.equals("yes")) {
									agent.startACRE();
								}
							} else {
								System.err.println( "No class defined for agent named \"" + entity + "\" SKIPPING" );
							}
						}
						else {
							System.err.println( "An agent with name: \"" + entity + "\" already exists: SKIPPING");
						}
					} catch (ASTRAClassNotFoundException e1) {
						System.err.println(e1.getMessage() + " SKIPPING");
					} catch (DuplicateAgentNameException e1) {
						System.err.println(e1.getMessage() + " SKIPPING");
					}

				}
			}
		}
		
		// Start the EIS service (if it is specified)
		if (service != null) service.eisStart();
		
		// Create and initalize the executor threads
		executors = new Executor[agents.size()];
		for (int i=0;i<executors.length;i++) {
			executors[i] = new Executor();
		}
		
		for (int j = 0; j < agents.size(); j++) {
			executors[j%executors.length].addAgent(agents.get(j));
		}
		
		for (int i=0;i<executors.length;i++) {
			new Thread(executors[i]).start();
		}
	}

	public List<Agent> agents() {
		return agents;
	}

	public static void main(String[] args) {
		if (args.length == 0) {
			System.out.println("This loader takes 1 argument: the name of the MAS configuration file to be loaded.");
			System.exit(1);
		}
		
		new Loader().load(args[0]);
	}

	public void shutdown() {
		for (Executor executor : executors) {
			executor.shutdown();
		}
		
	}
	
}
